/*
    Copyright (C) 2011 Denis G. <the.same.denis@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

    This program contains code from GNU Wget,
    released under the GNU GPL (http://gnu.org/).
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MALLOC_CHECK_ = 1
#define countof(A) sizeof(A)/sizeof((A)[0])

struct DATASTRUCT {int code; char *name; char *command;};
 
extern char *procname;
extern struct DATASTRUCT data[];

enum parse_line {
 line_ok,
 line_empty,
 line_syntax_error,
 line_unknown_command
};

char *
read_whole_line (FILE *fp)
{
    int length = 0;
    int bufsize = 82;
    char *line = (char *)malloc (bufsize);

    while (fgets (line + length, bufsize - length, fp))
    {
        length += strlen (line + length);
        if (length == 0)
    /* Possible for example when reading from a binary file where
            a line begins with \0.  */
            continue;

        if (line[length - 1] == '\n')
            break;

      /* fgets() guarantees to read the whole line, or to use up the
        space we've given it.  We can double the buffer
        unconditionally.  */
        bufsize <<= 1;
        line = realloc (line, bufsize);
    }
    if (length == 0 || ferror (fp))
    {
        free (line);
        return NULL;
    }
    if (length + 1 < bufsize)
    /* Relieve the memory from our exponential greediness.  We say
        `length + 1' because the terminating \0 is not included in
        LENGTH.  We don't need to zero-terminate the string ourselves,
        though, because fgets() does that.  */
        line = realloc (line, length + 1);
    return line;
}

char *
strdupdelim (const char *beg, const char *end)
{
    char *res = (char *)malloc (end - beg + 1);
    memcpy (res, beg, end - beg);
    res[end - beg] = '\0';
    return res;
}

int
command_by_name (const char *cmdname)
{
  /* Use binary search for speed.  Wget has ~100 commands, which
    guarantees a worst case performance of 7 string comparisons.  */
    int lo = 1, hi = data[0].code - 1, mid;
/*  while (lo <= hi)
    {
        int mid = (lo + hi) >> 1;
        printf ("%s - %s : %d\n", cmdname, data[mid].name, mid);
        int cmp = strcasecmp (cmdname, data[mid].name);
        if (cmp < 0)
            hi = mid - 1;
        else if (cmp > 0)
            lo = mid + 1;
        else 
            return mid;
    }   */

    for (mid = lo; mid <= hi; mid++)
    {
        if (strcasecmp (cmdname, data[mid].name) == 0)
            return mid;
    }
    return -1;
}

static enum parse_line
parse_line (const char *line, char **com, char **val, int *com_ind)
{
    const char *p;
    const char *end = line + strlen (line);
    const char *cmdstart, *cmdend;
    const char *valstart, *valend;

    /* Skip leading and trailing whitespace.  */
    while (*line && isspace (*line))
        ++line;
    while (end > line && isspace (end[-1]))
        --end;

    /* Skip empty lines and comments.  */
    if (!*line || *line == '#')
        return line_empty;

    p = line;

    cmdstart = p;
    while (p < end && (isalnum (*p) || *p == '_' || *p == '-'))
        ++p;
    cmdend = p;

    /* Skip '=', as well as any space before or after it. */
    while (p < end && isspace (*p))
        ++p;
    if (p == end || *p != '=')
        return line_syntax_error;
    ++p;
    while (p < end && isspace (*p))
        ++p;

    valstart = p;
    valend   = end;

  /* The syntax is valid (even though the command might not be).  Fill
    in the command name and value.  */
    *com = strdupdelim (cmdstart, cmdend);
    *val = strdupdelim (valstart, valend);

    *com_ind = command_by_name(*com);
    if (*com_ind == -1)
        return line_unknown_command;
    
    return line_ok;
}

int
set_value(int *command_index, char *value)
{
    data[*command_index].command = (char *)malloc (sizeof(char) * (strlen(value)+1));
    strcpy (data[*command_index].command, value);
    return 0;
}

int
read_config (char *config_file)
{
    FILE *fp;
    char *line;
    int ln;
    
    if ((fp=fopen(config_file, "r")) > 0)
    {
        ln = 1;
        while ((line = read_whole_line (fp)) != NULL)
        {
            char *com = NULL, *val = NULL;
            int com_ind;
            
            /* Parse the line.  */
            switch (parse_line (line, &com, &val, &com_ind))
            {
                case line_ok:
                    /* If everything is OK, set the value.  */
                    set_value(&com_ind, val);
                    break;
                case line_syntax_error:
                    fprintf (stderr, "%s: Syntax error in configuration file at line %d.\n", procname, ln);
                    break;
                case line_unknown_command:
                    fprintf (stderr, "%s: Unknown command in configuration file at line %d.\n", procname, ln);
                    break;
                case line_empty:
                    break;
                default:
                    abort ();
            }
            free (com);
            free (val);
            free (line);
            ++ln;
        }
        fclose (fp);
    } else { 
        return 0;
    }
    return 1;
}

